package com.jfinalplus.util;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.regex.Pattern;

public final class StringUtils {

	// Empty checks
	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Checks if a String is empty ("") or null.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.isEmpty(null)      = true
	 * StringUtils.isEmpty("")        = true
	 * StringUtils.isEmpty(" ")       = false
	 * StringUtils.isEmpty("bob")     = false
	 * StringUtils.isEmpty("  bob  ") = false
	 * </pre>
	 */
	public static boolean isEmpty(final String s) {
		return s == null || s.length() == 0;
	}

	/**
	 * <p>
	 * Checks if a String is not empty ("") and not null.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.isNotEmpty(null)      = false
	 * StringUtils.isNotEmpty("")        = false
	 * StringUtils.isNotEmpty(" ")       = true
	 * StringUtils.isNotEmpty("bob")     = true
	 * StringUtils.isNotEmpty("  bob  ") = true
	 * </pre>
	 */
	public static boolean isNotEmpty(final String s) {
		return s != null && s.length() > 0;
	}

	/**
	 * <p>
	 * Checks if a String is whitespace, empty ("") or null.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.isBlank(null)      = true
	 * StringUtils.isBlank("")        = true
	 * StringUtils.isBlank(" ")       = true
	 * StringUtils.isBlank("bob")     = false
	 * StringUtils.isBlank("  bob  ") = false
	 * </pre>
	 */
	public static boolean isBlank(final String s) {
		int len;
		if (s == null || (len = s.length()) == 0) { return true; }
		for (int i = 0; i < len; i++) {
			if (Character.isWhitespace(s.charAt(i)) == false) { return false; }
		}
		return true;
	}

	/**
	 * <p>
	 * Checks if a String is not empty (""), not null and not whitespace
	 * only.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.isNotBlank(null)      = false
	 * StringUtils.isNotBlank("")        = false
	 * StringUtils.isNotBlank(" ")       = false
	 * StringUtils.isNotBlank("bob")     = true
	 * StringUtils.isNotBlank("  bob  ") = true
	 * </pre>
	 */
	public static boolean isNotBlank(final String s) {
		return !isBlank(s);
	}

	public static byte[] getBytes(String s, String charsetName) {
		if (s == null) { return null; }
		try {
			return s.getBytes(charsetName);
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
	}

	public static byte[] getUtf8Bytes(String s) {
		return s.getBytes(Charset.forName("UTF-8"));
	}

	// Trim
	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Removes control characters (char &lt;= 32) from both ends of this String,
	 * handling {@code null} by returning {@code null}.
	 * </p>
	 *
	 * <p>
	 * The String is trimmed using {@link String#trim()}. Trim removes start and
	 * end characters &lt;= 32.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.trim(null)          = null
	 * StringUtils.trim("")            = ""
	 * StringUtils.trim("     ")       = ""
	 * StringUtils.trim("abc")         = "abc"
	 * StringUtils.trim("    abc    ") = "abc"
	 * </pre>
	 */
	public static String trim(final String s) {
		return s == null ? null : s.trim();
	}

	/**
	 * <p>
	 * Removes control characters (char &lt;= 32) from both ends of this String
	 * returning {@code null} if the String is empty ("") after the trim or if
	 * it is {@code null}.
	 *
	 * <p>
	 * The String is trimmed using {@link String#trim()}. Trim removes start and
	 * end characters &lt;= 32.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.trimToNull(null)          = null
	 * StringUtils.trimToNull("")            = null
	 * StringUtils.trimToNull("     ")       = null
	 * StringUtils.trimToNull("abc")         = "abc"
	 * StringUtils.trimToNull("    abc    ") = "abc"
	 * </pre>
	 */
	public static String trimToNull(final String s) {
		final String ts = trim(s);
		return ts == null || ts.length() == 0 ? null : ts;
	}

	/**
	 * <p>
	 * Removes control characters (char &lt;= 32) from both ends of this String
	 * returning an empty String ("") if the String is empty ("") after the trim
	 * or if it is {@code null}.
	 *
	 * <p>
	 * The String is trimmed using {@link String#trim()}. Trim removes start and
	 * end characters &lt;= 32.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.trimToEmpty(null)          = ""
	 * StringUtils.trimToEmpty("")            = ""
	 * StringUtils.trimToEmpty("     ")       = ""
	 * StringUtils.trimToEmpty("abc")         = "abc"
	 * StringUtils.trimToEmpty("    abc    ") = "abc"
	 * </pre>
	 */
	public static String trimToEmpty(final String s) {
		return s == null ? "" : s.trim();
	}

	public static String[] trim(String[] ss) {
		if (ss != null) {
			for (int i = 0; i < ss.length; i++) {
				String string = ss[i];
				if (string != null) {
					ss[i] = string.trim();
				}
			}
		}
		return ss;
	}

	// Defaults
	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Returns either the passed in String, or if the String is {@code null},
	 * the value of {@code defaultStr}.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.defaultIfNull(null, "NULL")  = "NULL"
	 * StringUtils.defaultIfNull("", "NULL")    = ""
	 * StringUtils.defaultIfNull("bat", "NULL") = "bat"
	 * </pre>
	 */
	public static String defaultIfNull(final String s, final String defaultStr) {
		return s == null ? defaultStr : s;
	}

	/**
	 * <p>
	 * Returns either the passed in String, or if the String is
	 * empty or {@code null}, the value of {@code defaultStr}.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.defaultIfEmpty(null, "NULL")  = "NULL"
	 * StringUtils.defaultIfEmpty("", "NULL")    = "NULL"
	 * StringUtils.defaultIfEmpty(" ", "NULL")   = " "
	 * StringUtils.defaultIfEmpty("bat", "NULL") = "bat"
	 * StringUtils.defaultIfEmpty("", null)      = null
	 * </pre>
	 */
	public static String defaultIfEmpty(final String s, final String defaultStr) {
		return isEmpty(s) ? defaultStr : s;
	}

	/**
	 * <p>
	 * Returns either the passed in String, or if the String is
	 * whitespace, empty ("") or {@code null}, the value of {@code defaultStr}.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.defaultIfBlank(null, "NULL")  = "NULL"
	 * StringUtils.defaultIfBlank("", "NULL")    = "NULL"
	 * StringUtils.defaultIfBlank(" ", "NULL")   = "NULL"
	 * StringUtils.defaultIfBlank("bat", "NULL") = "bat"
	 * StringUtils.defaultIfBlank("", null)      = null
	 * </pre>
	 */
	public static String defaultIfBlank(final String s, final String defaultStr) {
		return isBlank(s) ? defaultStr : s;
	}

	// Equals
	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Compares two CharSequences, returning {@code true} if they represent
	 * equal sequences of characters.
	 * </p>
	 *
	 * <p>
	 * {@code null}s are handled without exceptions. Two {@code null} references
	 * are considered to be equal. The comparison is case sensitive.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.equals(null, null)   = true
	 * StringUtils.equals(null, "abc")  = false
	 * StringUtils.equals("abc", null)  = false
	 * StringUtils.equals("abc", "abc") = true
	 * StringUtils.equals("abc", "ABC") = false
	 * </pre>
	 */
	public static boolean equals(final String s1, final String s2) {
		if (s1 == s2) { return true; }
		if (s1 == null || s2 == null) { return false; }
		return s1.equals(s2);
	}

	/**
	 * <p>
	 * Compares two CharSequences, returning {@code true} if they represent
	 * equal sequences of characters, ignoring case.
	 * </p>
	 *
	 * <p>
	 * {@code null}s are handled without exceptions. Two {@code null} references
	 * are considered equal. Comparison is case insensitive.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.equalsIgnoreCase(null, null)   = true
	 * StringUtils.equalsIgnoreCase(null, "abc")  = false
	 * StringUtils.equalsIgnoreCase("abc", null)  = false
	 * StringUtils.equalsIgnoreCase("abc", "abc") = true
	 * StringUtils.equalsIgnoreCase("abc", "ABC") = true
	 * </pre>
	 */
	public static boolean equalsIgnoreCase(final String s1, final String s2) {
		if (s1 == null || s2 == null) {
			return s1 == s2;
		} else if (s1 == s2) {
			return true;
		} else if (s1.length() != s2.length()) {
			return false;
		} else {
			return s1.equals(s2);
		}
	}

	// startsWith
	// -----------------------------------------------------------------------

	/**
	 * <p>
	 * Check if a String starts with a specified prefix.
	 * </p>
	 *
	 * <p>
	 * {@code null}s are handled without exceptions. Two {@code null} references
	 * are considered to be equal. The comparison is case sensitive.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.startsWith(null, null)      = true
	 * StringUtils.startsWith(null, "abc")     = false
	 * StringUtils.startsWith("abcdef", null)  = false
	 * StringUtils.startsWith("abcdef", "abc") = true
	 * StringUtils.startsWith("ABCDEF", "abc") = false
	 * </pre>
	 *
	 * @see java.lang.String#startsWith(String)
	 * @param s
	 *            the String to check, may be null
	 * @param prefix
	 *            the prefix to find, may be null
	 * @return {@code true} if the String starts with the prefix, case
	 *         sensitive, or both {@code null}
	 */
	public static boolean startsWith(final String s, final String prefix) {
		return startsWith(s, prefix, false);
	}

	/**
	 * <p>
	 * Case insensitive check if a String starts with a specified prefix.
	 * </p>
	 *
	 * <p>
	 * {@code null}s are handled without exceptions. Two {@code null} references
	 * are considered to be equal. The comparison is case insensitive.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.startsWithIgnoreCase(null, null)      = true
	 * StringUtils.startsWithIgnoreCase(null, "abc")     = false
	 * StringUtils.startsWithIgnoreCase("abcdef", null)  = false
	 * StringUtils.startsWithIgnoreCase("abcdef", "abc") = true
	 * StringUtils.startsWithIgnoreCase("ABCDEF", "abc") = true
	 * </pre>
	 *
	 * @see java.lang.String#startsWith(String)
	 */
	public static boolean startsWithIgnoreCase(final String s, final String prefix) {
		return startsWith(s, prefix, true);
	}

	/**
	 * <p>
	 * Check if a String starts with a specified prefix (optionally case
	 * insensitive).
	 * </p>
	 *
	 * @see java.lang.String#startsWith(String)
	 */
	private static boolean startsWith(final String s, final String prefix, final boolean ignoreCase) {
		if (s == null || prefix == null) { return s == null && prefix == null; }
		if (prefix.length() > s.length()) { return false; }
		return s.toString().regionMatches(ignoreCase, 0, prefix.toString(), 0, prefix.length());
	}

	public static int startsWithOne(String s, String... dest) {
		for (int i = 0; i < dest.length; i++) {
			String m = dest[i];
			if (m != null) {
				if (s.startsWith(m)) return i;
			}
		}
		return -1;
	}

	public static int startsWithOneIgnoreCase(String s, String... dest) {
		for (int i = 0; i < dest.length; i++) {
			String m = dest[i];
			if (m != null) {
				if (startsWithIgnoreCase(s, m)) return i;
			}
		}
		return -1;
	}

	public static boolean startsWithChar(final String s, final char c) {
		if (s == null || s.length() == 0) { return false; }
		return s.charAt(0) == c;
	}

	// endsWith
	// -----------------------------------------------------------------------

	/**
	 * <p>
	 * Check if a String ends with a specified suffix.
	 * </p>
	 *
	 * <p>
	 * {@code null}s are handled without exceptions. Two {@code null} references
	 * are considered to be equal. The comparison is case sensitive.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.endsWith(null, null)      = true
	 * StringUtils.endsWith(null, "def")     = false
	 * StringUtils.endsWith("abcdef", null)  = false
	 * StringUtils.endsWith("abcdef", "def") = true
	 * StringUtils.endsWith("ABCDEF", "def") = false
	 * StringUtils.endsWith("ABCDEF", "cde") = false
	 * </pre>
	 *
	 * @see java.lang.String#endsWith(String)
	 */
	public static boolean endsWith(final String s, final String suffix) {
		return endsWith(s, suffix, false);
	}

	/**
	 * <p>
	 * Case insensitive check if a String ends with a specified suffix.
	 * </p>
	 *
	 * <p>
	 * {@code null}s are handled without exceptions. Two {@code null} references
	 * are considered to be equal. The comparison is case insensitive.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.endsWithIgnoreCase(null, null)      = true
	 * StringUtils.endsWithIgnoreCase(null, "def")     = false
	 * StringUtils.endsWithIgnoreCase("abcdef", null)  = false
	 * StringUtils.endsWithIgnoreCase("abcdef", "def") = true
	 * StringUtils.endsWithIgnoreCase("ABCDEF", "def") = true
	 * StringUtils.endsWithIgnoreCase("ABCDEF", "cde") = false
	 * </pre>
	 *
	 * @see java.lang.String#endsWith(String)
	 */
	public static boolean endsWithIgnoreCase(final String s, final String suffix) {
		return endsWith(s, suffix, true);
	}

	/**
	 * <p>
	 * Check if a String ends with a specified suffix (optionally case
	 * insensitive).
	 * </p>
	 *
	 * @see java.lang.String#endsWith(String)
	 */
	private static boolean endsWith(final String s, final String suffix, final boolean ignoreCase) {
		if (s == null || suffix == null) { return s == null && suffix == null; }
		if (suffix.length() > s.length()) { return false; }
		final int strOffset = s.length() - suffix.length();
		return s.toString().regionMatches(ignoreCase, strOffset, suffix.toString(), 0, suffix.length());
	}

	public static int endsWithOne(String src, String... dest) {
		for (int i = 0; i < dest.length; i++) {
			String m = dest[i];
			if (m != null) {
				if (src.endsWith(m)) return i;
			}
		}
		return -1;
	}

	public static int endsWithOneIgnoreCase(String s, String... dest) {
		for (int i = 0; i < dest.length; i++) {
			String m = dest[i];
			if (m != null) {
				if (endsWithIgnoreCase(s, m)) return i;
			}
		}
		return -1;
	}

	public static boolean endsWithChar(final String s, final char c) {
		if (s == null || s.length() == 0) { return false; }
		return s.charAt(s.length() - 1) == c;
	}

	// ---------------------------------------------------------------- indexof and ignore cases

	/**
	 * Finds first occurrence of a substring in the given source but within limited range [start, end).
	 * It is fastest possible code, but still original <code>String.indexOf(String, int)</code>
	 * is much faster (since it uses char[] value directly) and should be used when no range is needed.
	 *
	 * @param s                source string for examination
	 * @param substr                substring to find
	 * @param startIndex        starting index
	 * @param endIndex                ending index
	 * @return index of founded substring or -1 if substring not found
	 */
	public static int indexOf(String s, String substr, int startIndex, int endIndex) {
		if (startIndex < 0) {
			startIndex = 0;
		}
		int srclen = s.length();
		if (endIndex > srclen) {
			endIndex = srclen;
		}
		int sublen = substr.length();
		if (sublen == 0) { return startIndex > srclen ? srclen : startIndex; }

		int total = endIndex - sublen + 1;
		char c = substr.charAt(0);
		mainloop: for (int i = startIndex; i < total; i++) {
			if (s.charAt(i) != c) {
				continue;
			}
			int j = 1;
			int k = i + 1;
			while (j < sublen) {
				if (substr.charAt(j) != s.charAt(k)) {
					continue mainloop;
				}
				j++;
				k++;
			}
			return i;
		}
		return -1;
	}

	/**
	 * Finds the first occurrence of a character in the given source but within limited range (start, end].
	 */
	public static int indexOf(String s, char c, int startIndex, int endIndex) {
		if (startIndex < 0) {
			startIndex = 0;
		}
		int srclen = s.length();
		if (endIndex > srclen) {
			endIndex = srclen;
		}
		for (int i = startIndex; i < endIndex; i++) {
			if (s.charAt(i) == c) { return i; }
		}
		return -1;
	}

	/**
	 * Finds the first occurrence of a character in the given source but within limited range (start, end].
	 */
	public static int indexOfIgnoreCase(String s, char c, int startIndex, int endIndex) {
		if (startIndex < 0) {
			startIndex = 0;
		}
		int srclen = s.length();
		if (endIndex > srclen) {
			endIndex = srclen;
		}
		c = Character.toLowerCase(c);
		for (int i = startIndex; i < endIndex; i++) {
			if (Character.toLowerCase(s.charAt(i)) == c) { return i; }
		}
		return -1;
	}

	/**
	 * Finds first index of a substring in the given source string with ignored case.
	 *
	 * @param s    source string for examination
	 * @param substr   substring to find
	 *
	 * @return index of founded substring or -1 if substring is not found
	 * @see #indexOfIgnoreCase(String, String, int)
	 */
	public static int indexOfIgnoreCase(String s, String substr) {
		return indexOfIgnoreCase(s, substr, 0, s.length());
	}

	/**
	 * Finds first index of a substring in the given source string with ignored
	 * case. This seems to be the fastest way doing this, with common string
	 * length and content (of course, with no use of Boyer-Mayer type of
	 * algorithms). Other implementations are slower: getting char array first,
	 * lower casing the source string, using String.regionMatch etc.
	 *
	 * @param s        source string for examination
	 * @param substr       substring to find
	 * @param startIndex starting index from where search begins
	 *
	 * @return index of founded substring or -1 if substring is not found
	 */
	public static int indexOfIgnoreCase(String s, String substr, int startIndex) {
		return indexOfIgnoreCase(s, substr, startIndex, s.length());
	}

	/**
	 * Finds first index of a substring in the given source string and range with
	 * ignored case.
	 *
	 * @param s                source string for examination
	 * @param substr                substring to find
	 * @param startIndex        starting index from where search begins
	 * @param endIndex                endint index
	 * @return index of founded substring or -1 if substring is not found
	 * @see #indexOfIgnoreCase(String, String, int)
	 */
	public static int indexOfIgnoreCase(String s, String substr, int startIndex, int endIndex) {
		if (startIndex < 0) {
			startIndex = 0;
		}
		int srclen = s.length();
		if (endIndex > srclen) {
			endIndex = srclen;
		}

		int sublen = substr.length();
		if (sublen == 0) { return startIndex > srclen ? srclen : startIndex; }
		substr = substr.toLowerCase();
		int total = endIndex - sublen + 1;
		char c = substr.charAt(0);
		mainloop: for (int i = startIndex; i < total; i++) {
			if (Character.toLowerCase(s.charAt(i)) != c) {
				continue;
			}
			int j = 1;
			int k = i + 1;
			while (j < sublen) {
				char source = Character.toLowerCase(s.charAt(k));
				if (substr.charAt(j) != source) {
					continue mainloop;
				}
				j++;
				k++;
			}
			return i;
		}
		return -1;
	}

	/**
	 * Finds the very first index of a substring from the specified array. It
	 * returns an int[2] where int[0] represents the substring index and int[1]
	 * represents position where substring was found. Returns <code>null</code> if
	 * noting found.
	 *
	 * @param s      source string
	 * @param arr    string array
	 */
	public static int[] indexOf(String s, String arr[]) {
		return indexOf(s, arr, 0);
	}

	/**
	 * Finds the very first index of a substring from the specified array. It
	 * returns an int[2] where int[0] represents the substring index and int[1]
	 * represents position where substring was found. Returns <code>null</code>
	 * if noting found.
	 *
	 * @param s      source string
	 * @param arr    string array
	 * @param start  starting position
	 */
	public static int[] indexOf(String s, String arr[], int start) {
		int arrLen = arr.length;
		int index = Integer.MAX_VALUE;
		int last = -1;
		for (int j = 0; j < arrLen; j++) {
			int i = s.indexOf(arr[j], start);
			if (i != -1) {
				if (i < index) {
					index = i;
					last = j;
				}
			}
		}
		return last == -1 ? null : new int[] { last, index };
	}

	/**
	 * Finds the very first index of a substring from the specified array. It
	 * returns an int[2] where int[0] represents the substring index and int[1]
	 * represents position where substring was found. Returns <code>null</code> if
	 * noting found.
	 *
	 * @param s      source string
	 * @param c      char array
	 */
	public static int[] indexOf(String s, char c[]) {
		return indexOf(s, c, 0);
	}

	/**
	 * Finds the very first index of a char from the specified array. It
	 * returns an int[2] where int[0] represents the char index and int[1]
	 * represents position where char was found. Returns <code>null</code>
	 * if noting found.
	 *
	 * @param s      source string
	 * @param c      char array
	 * @param start  starting position
	 */
	public static int[] indexOf(String s, char c[], int start) {
		int arrLen = c.length;
		int index = Integer.MAX_VALUE;
		int last = -1;
		for (int j = 0; j < arrLen; j++) {
			int i = s.indexOf(c[j], start);
			if (i != -1) {
				if (i < index) {
					index = i;
					last = j;
				}
			}
		}
		return last == -1 ? null : new int[] { last, index };
	}

	/**
	 * Finds the very first index of a substring from the specified array. It
	 * returns an int[2] where int[0] represents the substring index and int[1]
	 * represents position where substring was found. Returns <code>null</code>
	 * if noting found.
	 *
	 * @param s      source string
	 * @param arr    string array
	 */
	public static int[] indexOfIgnoreCase(String s, String arr[]) {
		return indexOfIgnoreCase(s, arr, 0);
	}

	/**
	 * Finds the very first index of a substring from the specified array. It
	 * returns an int[2] where int[0] represents the substring index and int[1]
	 * represents position where substring was found. Returns <code>null</code>
	 * if noting found.
	 *
	 * @param s      source string
	 * @param arr    string array
	 * @param start  starting position
	 */
	public static int[] indexOfIgnoreCase(String s, String arr[], int start) {
		int arrLen = arr.length;
		int index = Integer.MAX_VALUE;
		int last = -1;
		for (int j = 0; j < arrLen; j++) {
			int i = indexOfIgnoreCase(s, arr[j], start);
			if (i != -1) {
				if (i < index) {
					index = i;
					last = j;
				}
			}
		}
		return last == -1 ? null : new int[] { last, index };
	}

	/**
	 * Finds last index of a substring in the given source string with ignored
	 * case.
	 *
	 * @param s      source string
	 * @param substr   substring to find
	 *
	 * @return last index of founded substring or -1 if substring is not found
	 * @see #indexOfIgnoreCase(String, String, int)
	 * @see #lastIndexOfIgnoreCase(String, String, int)
	 */
	public static int lastIndexOfIgnoreCase(String s, String substr) {
		return lastIndexOfIgnoreCase(s, substr, s.length(), 0);
	}

	/**
	 * Finds last index of a substring in the given source string with ignored
	 * case.
	 *
	 * @param s        source string for examination
	 * @param substr       substring to find
	 * @param startIndex starting index from where search begins
	 *
	 * @return last index of founded substring or -1 if substring is not found
	 * @see #indexOfIgnoreCase(String, String, int)
	 */
	public static int lastIndexOfIgnoreCase(String s, String substr, int startIndex) {
		return lastIndexOfIgnoreCase(s, substr, startIndex, 0);
	}

	/**
	 * Finds last index of a substring in the given source string with ignored
	 * case in specified range.
	 *
	 * @param s                source to examine
	 * @param sub                substring to find
	 * @param startIndex        starting index
	 * @param endIndex                end index
	 * @return last index of founded substring or -1 if substring is not found
	 */
	public static int lastIndexOfIgnoreCase(String s, String sub, int startIndex, int endIndex) {
		int sublen = sub.length();
		int srclen = s.length();
		if (sublen == 0) { return startIndex > srclen ? srclen : (startIndex < -1 ? -1 : startIndex); }
		sub = sub.toLowerCase();
		int total = srclen - sublen;
		if (total < 0) { return -1; }
		if (startIndex >= total) {
			startIndex = total;
		}
		if (endIndex < 0) {
			endIndex = 0;
		}
		char c = sub.charAt(0);
		mainloop: for (int i = startIndex; i >= endIndex; i--) {
			if (Character.toLowerCase(s.charAt(i)) != c) {
				continue;
			}
			int j = 1;
			int k = i + 1;
			while (j < sublen) {
				char source = Character.toLowerCase(s.charAt(k));
				if (sub.charAt(j) != source) {
					continue mainloop;
				}
				j++;
				k++;
			}
			return i;
		}
		return -1;
	}

	/**
	 * Finds last index of a substring in the given source string in specified range [end, start]
	 * See {@link #indexOf(String, String, int, int)}  for details about the speed.
	 *
	 * @param s                source to examine
	 * @param sub                substring to find
	 * @param startIndex        starting index
	 * @param endIndex                end index
	 * @return last index of founded substring or -1 if substring is not found
	 */
	public static int lastIndexOf(String s, String sub, int startIndex, int endIndex) {
		int sublen = sub.length();
		int srclen = s.length();
		if (sublen == 0) { return startIndex > srclen ? srclen : (startIndex < -1 ? -1 : startIndex); }
		int total = srclen - sublen;
		if (total < 0) { return -1; }
		if (startIndex >= total) {
			startIndex = total;
		}
		if (endIndex < 0) {
			endIndex = 0;
		}
		char c = sub.charAt(0);
		mainloop: for (int i = startIndex; i >= endIndex; i--) {
			if (s.charAt(i) != c) {
				continue;
			}
			int j = 1;
			int k = i + 1;
			while (j < sublen) {
				if (sub.charAt(j) != s.charAt(k)) {
					continue mainloop;
				}
				j++;
				k++;
			}
			return i;
		}
		return -1;
	}

	/**
	 * Finds last index of a character in the given source string in specified range [end, start]
	 */
	public static int lastIndexOf(String s, char c, int startIndex, int endIndex) {
		int total = s.length() - 1;
		if (total < 0) { return -1; }
		if (startIndex >= total) {
			startIndex = total;
		}
		if (endIndex < 0) {
			endIndex = 0;
		}
		for (int i = startIndex; i >= endIndex; i--) {
			if (s.charAt(i) == c) { return i; }
		}
		return -1;
	}

	/**
	 * Finds last index of a character in the given source string in specified range [end, start]
	 */
	public static int lastIndexOfIgnoreCase(String s, char c, int startIndex, int endIndex) {
		int total = s.length() - 1;
		if (total < 0) { return -1; }
		if (startIndex >= total) {
			startIndex = total;
		}
		if (endIndex < 0) {
			endIndex = 0;
		}
		c = Character.toLowerCase(c);
		for (int i = startIndex; i >= endIndex; i--) {
			if (Character.toLowerCase(s.charAt(i)) == c) { return i; }
		}
		return -1;
	}

	/**
	 * Finds the very last index of a substring from the specified array. It
	 * returns an int[2] where int[0] represents the substring index and int[1]
	 * represents position where substring was found. Returns <code>null</code>
	 * if noting found.
	 *
	 * @param s      source string
	 * @param arr    string array
	 */
	public static int[] lastIndexOf(String s, String arr[]) {
		return lastIndexOf(s, arr, s.length());
	}

	/**
	 * Finds the very last index of a substring from the specified array. It
	 * returns an int[2] where int[0] represents the substring index and int[1]
	 * represents position where substring was found. Returns <code>null</code>
	 * if noting found.
	 *
	 * @param s         source string
	 * @param arr       string array
	 * @param fromIndex starting position
	 */
	public static int[] lastIndexOf(String s, String arr[], int fromIndex) {
		int arrLen = arr.length;
		int index = -1;
		int last = -1;
		for (int j = 0; j < arrLen; j++) {
			int i = s.lastIndexOf(arr[j], fromIndex);
			if (i != -1) {
				if (i > index) {
					index = i;
					last = j;
				}
			}
		}
		return last == -1 ? null : new int[] { last, index };
	}

	/**
	 * Finds the very last index of a substring from the specified array. It
	 * returns an int[2] where int[0] represents the substring index and int[1]
	 * represents position where substring was found. Returns <code>null</code>
	 * if noting found.
	 *
	 * @param s      source string
	 * @param c      char array
	 */
	public static int[] lastIndexOf(String s, char c[]) {
		return lastIndexOf(s, c, s.length());
	}

	/**
	 * Finds the very last index of a substring from the specified array. It
	 * returns an int[2] where int[0] represents the substring index and int[1]
	 * represents position where substring was found. Returns <code>null</code>
	 * if noting found.
	 *
	 * @param s         source string
	 * @param c         char array
	 * @param fromIndex starting position
	 */
	public static int[] lastIndexOf(String s, char c[], int fromIndex) {
		int arrLen = c.length;
		int index = -1;
		int last = -1;
		for (int j = 0; j < arrLen; j++) {
			int i = s.lastIndexOf(c[j], fromIndex);
			if (i != -1) {
				if (i > index) {
					index = i;
					last = j;
				}
			}
		}
		return last == -1 ? null : new int[] { last, index };
	}

	/**
	 * Finds the very last index of a substring from the specified array. It
	 * returns an int[2] where int[0] represents the substring index and int[1]
	 * represents position where substring was found. Returns <code>null</code>
	 * if noting found.
	 *
	 * @param s      source string
	 * @param arr    string array
	 *
	 * @return int[2]
	 */
	public static int[] lastIndexOfIgnoreCase(String s, String arr[]) {
		return lastIndexOfIgnoreCase(s, arr, s.length());
	}

	/**
	 * Finds the very last index of a substring from the specified array. It
	 * returns an int[2] where int[0] represents the substring index and int[1]
	 * represents position where substring was found. Returns <code>null</code>
	 * if noting found.
	 *
	 * @param s         source string
	 * @param arr       string array
	 * @param fromIndex starting position
	 */
	public static int[] lastIndexOfIgnoreCase(String s, String arr[], int fromIndex) {
		int arrLen = arr.length;
		int index = -1;
		int last = -1;
		for (int j = 0; j < arrLen; j++) {
			int i = lastIndexOfIgnoreCase(s, arr[j], fromIndex);
			if (i != -1) {
				if (i > index) {
					index = i;
					last = j;
				}
			}
		}
		return last == -1 ? null : new int[] { last, index };
	}

	// Substring
	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Gets a substring from the specified String avoiding exceptions.
	 * </p>
	 *
	 * <p>
	 * A negative start position can be used to start {@code n} characters from
	 * the end of the String.
	 * </p>
	 *
	 * <p>
	 * A {@code null} String will return {@code null}. An empty ("") String will
	 * return "".
	 * </p>
	 *
	 * <pre>
	 * StringUtils.substring(null, *)   = null
	 * StringUtils.substring("", *)     = ""
	 * StringUtils.substring("abc", 0)  = "abc"
	 * StringUtils.substring("abc", 2)  = "c"
	 * StringUtils.substring("abc", 4)  = ""
	 * StringUtils.substring("abc", -2) = "bc"
	 * StringUtils.substring("abc", -4) = "abc"
	 * </pre>
	 */
	public static String substring(final String s, int start) {
		if (s == null) { return null; }

		// handle negatives, which means last n characters
		if (start < 0) {
			start = s.length() + start; // remember start is negative
		}

		if (start < 0) {
			start = 0;
		}
		if (start > s.length()) { return ""; }

		return s.substring(start);
	}

	/**
	 * <p>
	 * Gets a substring from the specified String avoiding exceptions.
	 * </p>
	 *
	 * <p>
	 * A negative start position can be used to start/end {@code n} characters
	 * from the end of the String.
	 * </p>
	 *
	 * <p>
	 * The returned substring starts with the character in the {@code start}
	 * position and ends before the {@code end} position. All position counting
	 * is zero-based -- i.e., to start at the beginning of the string use
	 * {@code start = 0}. Negative start and end positions can be used to
	 * specify offsets relative to the end of the String.
	 * </p>
	 *
	 * <p>
	 * If {@code start} is not strictly to the left of {@code end}, "" is
	 * returned.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.substring(null, *, *)    = null
	 * StringUtils.substring("", * ,  *)    = "";
	 * StringUtils.substring("abc", 0, 2)   = "ab"
	 * StringUtils.substring("abc", 2, 0)   = ""
	 * StringUtils.substring("abc", 2, 4)   = "c"
	 * StringUtils.substring("abc", 4, 6)   = ""
	 * StringUtils.substring("abc", 2, 2)   = ""
	 * StringUtils.substring("abc", -2, -1) = "b"
	 * StringUtils.substring("abc", -4, 2)  = "ab"
	 * </pre>
	 */
	public static String substring(final String s, int start, int end) {
		if (s == null) { return null; }

		// handle negatives
		if (end < 0) {
			end = s.length() + end; // remember end is negative
		}
		if (start < 0) {
			start = s.length() + start; // remember start is negative
		}

		// check length next
		if (end > s.length()) {
			end = s.length();
		}

		// if start is greater than end, return ""
		if (start > end) { return ""; }

		if (start < 0) {
			start = 0;
		}
		if (end < 0) {
			end = 0;
		}

		return s.substring(start, end);
	}

	// SubStringAfter/SubStringBefore
	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Gets the substring before the first occurrence of a separator. The
	 * separator is not returned.
	 * </p>
	 *
	 * <p>
	 * A {@code null} string input will return {@code null}. An empty ("")
	 * string input will return the empty string. A {@code null} separator will
	 * return the input string.
	 * </p>
	 *
	 * <p>
	 * If nothing is found, the string input is returned.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.substringBefore(null, *)      = null
	 * StringUtils.substringBefore("", *)        = ""
	 * StringUtils.substringBefore("abc", "a")   = ""
	 * StringUtils.substringBefore("abcba", "b") = "a"
	 * StringUtils.substringBefore("abc", "c")   = "ab"
	 * StringUtils.substringBefore("abc", "d")   = "abc"
	 * StringUtils.substringBefore("abc", "")    = ""
	 * StringUtils.substringBefore("abc", null)  = "abc"
	 * </pre>
	 */
	public static String substringBefore(final String s, final String separator) {
		if (isEmpty(s) || separator == null) { return s; }
		if (separator.isEmpty()) { return ""; }
		final int pos = s.indexOf(separator);
		if (pos < 0) { return s; }
		return s.substring(0, pos);
	}

	/**
	 * <p>
	 * Gets the substring after the first occurrence of a separator. The
	 * separator is not returned.
	 * </p>
	 *
	 * <p>
	 * A {@code null} string input will return {@code null}. An empty ("")
	 * string input will return the empty string. A {@code null} separator will
	 * return the empty string if the input string is not {@code null}.
	 * </p>
	 *
	 * <p>
	 * If nothing is found, the empty string is returned.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.substringAfter(null, *)      = null
	 * StringUtils.substringAfter("", *)        = ""
	 * StringUtils.substringAfter(*, null)      = ""
	 * StringUtils.substringAfter("abc", "a")   = "bc"
	 * StringUtils.substringAfter("abcba", "b") = "cba"
	 * StringUtils.substringAfter("abc", "c")   = ""
	 * StringUtils.substringAfter("abc", "d")   = ""
	 * StringUtils.substringAfter("abc", "")    = "abc"
	 * </pre>
	 */
	public static String substringAfter(final String s, final String separator) {
		if (isEmpty(s)) { return s; }
		if (separator == null) { return ""; }
		final int pos = s.indexOf(separator);
		if (pos < 0) { return ""; }
		return s.substring(pos + separator.length());
	}

	/**
	 * <p>
	 * Gets the substring before the last occurrence of a separator. The
	 * separator is not returned.
	 * </p>
	 *
	 * <p>
	 * A {@code null} string input will return {@code null}. An empty ("")
	 * string input will return the empty string. An empty or {@code null}
	 * separator will return the input string.
	 * </p>
	 *
	 * <p>
	 * If nothing is found, the string input is returned.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.substringBeforeLast(null, *)      = null
	 * StringUtils.substringBeforeLast("", *)        = ""
	 * StringUtils.substringBeforeLast("abcba", "b") = "abc"
	 * StringUtils.substringBeforeLast("abc", "c")   = "ab"
	 * StringUtils.substringBeforeLast("a", "a")     = ""
	 * StringUtils.substringBeforeLast("a", "z")     = "a"
	 * StringUtils.substringBeforeLast("a", null)    = "a"
	 * StringUtils.substringBeforeLast("a", "")      = "a"
	 * </pre>
	 */
	public static String substringBeforeLast(final String s, final String separator) {
		if (isEmpty(s) || isEmpty(separator)) { return s; }
		final int pos = s.lastIndexOf(separator);
		if (pos < 0) { return s; }
		return s.substring(0, pos);
	}

	/**
	 * <p>
	 * Gets the substring after the last occurrence of a separator. The
	 * separator is not returned.
	 * </p>
	 *
	 * <p>
	 * A {@code null} string input will return {@code null}. An empty ("")
	 * string input will return the empty string. An empty or {@code null}
	 * separator will return the empty string if the input string is not
	 * {@code null}.
	 * </p>
	 *
	 * <p>
	 * If nothing is found, the empty string is returned.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.substringAfterLast(null, *)      = null
	 * StringUtils.substringAfterLast("", *)        = ""
	 * StringUtils.substringAfterLast(*, "")        = ""
	 * StringUtils.substringAfterLast(*, null)      = ""
	 * StringUtils.substringAfterLast("abc", "a")   = "bc"
	 * StringUtils.substringAfterLast("abcba", "b") = "a"
	 * StringUtils.substringAfterLast("abc", "c")   = ""
	 * StringUtils.substringAfterLast("a", "a")     = ""
	 * StringUtils.substringAfterLast("a", "z")     = ""
	 * </pre>
	 */
	public static String substringAfterLast(final String s, final String separator) {
		if (isEmpty(s)) { return s; }
		if (isEmpty(separator)) { return ""; }
		final int pos = s.lastIndexOf(separator);
		if (pos < 0 || pos == s.length() - separator.length()) { return ""; }
		return s.substring(pos + separator.length());
	}

	// Substring between
	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Gets the String that is nested in between two instances of the same
	 * String.
	 * </p>
	 *
	 * <p>
	 * A {@code null} input String returns {@code null}. A {@code null} tag
	 * returns {@code null}.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.substringBetween(null, *)            = null
	 * StringUtils.substringBetween("", "")             = ""
	 * StringUtils.substringBetween("", "tag")          = null
	 * StringUtils.substringBetween("tagabctag", null)  = null
	 * StringUtils.substringBetween("tagabctag", "")    = ""
	 * StringUtils.substringBetween("tagabctag", "tag") = "abc"
	 * </pre>
	 */
	public static String substringBetween(final String s, final String tag) {
		return substringBetween(s, tag, tag);
	}

	/**
	 * <p>
	 * Gets the String that is nested in between two Strings. Only the first
	 * match is returned.
	 * </p>
	 *
	 * <p>
	 * A {@code null} input String returns {@code null}. A {@code null}
	 * open/close returns {@code null} (no match). An empty ("") open and close
	 * returns an empty string.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.substringBetween("wx[b]yz", "[", "]") = "b"
	 * StringUtils.substringBetween(null, *, *)          = null
	 * StringUtils.substringBetween(*, null, *)          = null
	 * StringUtils.substringBetween(*, *, null)          = null
	 * StringUtils.substringBetween("", "", "")          = ""
	 * StringUtils.substringBetween("", "", "]")         = null
	 * StringUtils.substringBetween("", "[", "]")        = null
	 * StringUtils.substringBetween("yabcz", "", "")     = ""
	 * StringUtils.substringBetween("yabcz", "y", "z")   = "abc"
	 * StringUtils.substringBetween("yabczyabcz", "y", "z")   = "abc"
	 * </pre>
	 */
	public static String substringBetween(final String s, final String open, final String close) {
		if (s == null || open == null || close == null) { return null; }
		final int start = s.indexOf(open);
		if (start >= 0) {
			final int end = s.indexOf(close, start + open.length());
			if (end >= 0) { return s.substring(start + open.length(), end); }
		}
		return null;
	}

	// Left/Right/Mid
	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Gets the leftmost {@code len} characters of a String.
	 * </p>
	 *
	 * <p>
	 * If {@code len} characters are not available, or the String is
	 * {@code null}, the String will be returned without an exception. An empty
	 * String is returned if len is negative.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.left(null, *)    = null
	 * StringUtils.left(*, -ve)     = ""
	 * StringUtils.left("", *)      = ""
	 * StringUtils.left("abc", 0)   = ""
	 * StringUtils.left("abc", 2)   = "ab"
	 * StringUtils.left("abc", 4)   = "abc"
	 * </pre>
	 */
	public static String left(final String s, final int len) {
		if (s == null) { return null; }
		if (len < 0) { return ""; }
		if (s.length() <= len) { return s; }
		return s.substring(0, len);
	}

	/**
	 * <p>
	 * Gets the rightmost {@code len} characters of a String.
	 * </p>
	 *
	 * <p>
	 * If {@code len} characters are not available, or the String is
	 * {@code null}, the String will be returned without an an exception. An
	 * empty String is returned if len is negative.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.right(null, *)    = null
	 * StringUtils.right(*, -ve)     = ""
	 * StringUtils.right("", *)      = ""
	 * StringUtils.right("abc", 0)   = ""
	 * StringUtils.right("abc", 2)   = "bc"
	 * StringUtils.right("abc", 4)   = "abc"
	 * </pre>
	 */
	public static String right(final String s, final int len) {
		if (s == null) { return null; }
		if (len < 0) { return ""; }
		if (s.length() <= len) { return s; }
		return s.substring(s.length() - len);
	}

	/**
	 * <p>
	 * Gets {@code len} characters from the middle of a String.
	 * </p>
	 *
	 * <p>
	 * If {@code len} characters are not available, the remainder of the String
	 * will be returned without an exception. If the String is {@code null},
	 * {@code null} will be returned. An empty String is returned if len is
	 * negative or exceeds the length of {@code str}.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.mid(null, *, *)    = null
	 * StringUtils.mid(*, *, -ve)     = ""
	 * StringUtils.mid("", 0, *)      = ""
	 * StringUtils.mid("abc", 0, 2)   = "ab"
	 * StringUtils.mid("abc", 0, 4)   = "abc"
	 * StringUtils.mid("abc", 2, 4)   = "c"
	 * StringUtils.mid("abc", 4, 2)   = ""
	 * StringUtils.mid("abc", -2, 2)  = "ab"
	 * </pre>
	 */
	public static String mid(final String s, int pos, final int len) {
		if (s == null) { return null; }
		if (len < 0 || pos > s.length()) { return ""; }
		if (pos < 0) {
			pos = 0;
		}
		if (s.length() <= pos + len) { return s.substring(pos); }
		return s.substring(pos, pos + len);
	}

	// Padding
	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Repeat a String {@code repeat} times to form a new String.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.repeat(null, 2) = null
	 * StringUtils.repeat("", 0)   = ""
	 * StringUtils.repeat("", 2)   = ""
	 * StringUtils.repeat("a", 3)  = "aaa"
	 * StringUtils.repeat("ab", 2) = "abab"
	 * StringUtils.repeat("a", -2) = ""
	 * </pre>
	 */
	public static String repeat(final String s, final int repeat) {
		if (s == null) { return null; }
		if (repeat <= 0) { return ""; }
		final int inputLength = s.length();
		if (repeat == 1 || inputLength == 0) { return s; }

		final int outputLength = inputLength * repeat;
		switch (inputLength) {
		case 1:
			return repeat(s.charAt(0), repeat);
		case 2:
			final char ch0 = s.charAt(0);
			final char ch1 = s.charAt(1);
			final char[] output2 = new char[outputLength];
			for (int i = repeat * 2 - 2; i >= 0; i--, i--) {
				output2[i] = ch0;
				output2[i + 1] = ch1;
			}
			return new String(output2);
		default:
			final StringBuilder buf = new StringBuilder(outputLength);
			for (int i = 0; i < repeat; i++) {
				buf.append(s);
			}
			return buf.toString();
		}
	}

	/**
	 * <p>
	 * Repeat a String {@code repeat} times to form a new String, with a String
	 * separator injected each time.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.repeat(null, null, 2) = null
	 * StringUtils.repeat(null, "x", 2)  = null
	 * StringUtils.repeat("", null, 0)   = ""
	 * StringUtils.repeat("", "", 2)     = ""
	 * StringUtils.repeat("", "x", 3)    = "xxx"
	 * StringUtils.repeat("?", ", ", 3)  = "?, ?, ?"
	 * </pre>
	 */
	public static String repeat(final String s, final String separator, final int repeat) {
		if (s == null || separator == null) { return repeat(s, repeat); }
		// given that repeat(String, int) is quite optimized, better to rely on
		// it than try and splice this into it
		final String result = repeat(s + separator, repeat);
		return removeEnd(result, separator);
	}

	/**
	 * <p>
	 * Returns padding using the specified delimiter repeated to a given length.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.repeat('e', 0)  = ""
	 * StringUtils.repeat('e', 3)  = "eee"
	 * StringUtils.repeat('e', -2) = ""
	 * </pre>
	 *
	 * <p>
	 * Note: this method doesn't not support padding with <a
	 * href="http://www.unicode.org/glossary/#supplementary_character">Unicode
	 * Supplementary Characters</a> as they require a pair of {@code char}s to
	 * be represented. If you are needing to support full I18N of your
	 * applications consider using {@link #repeat(String, int)} instead.
	 * </p>
	 * @see #repeat(String, int)
	 */
	public static String repeat(final char ch, final int repeat) {
		final char[] buf = new char[repeat];
		for (int i = repeat - 1; i >= 0; i--) {
			buf[i] = ch;
		}
		return new String(buf);
	}

	public static String leftPad(String str, int size) {
		return leftPad(str, size, ' ');
	}

	public static String leftPad(String str, int size, char padChar) {
		if (str == null) { return null; }
		int pads = size - str.length();
		if (pads <= 0) { return str; }
		if (pads > 8192) { return leftPad(str, size, String.valueOf(padChar)); }
		return repeat(padChar, pads).concat(str);
	}

	public static String leftPad(String str, int size, String padStr) {
		if (str == null) { return null; }
		if (isEmpty(padStr)) {
			padStr = " ";
		}
		int padLen = padStr.length();
		int strLen = str.length();
		int pads = size - strLen;
		if (pads <= 0) { return str; }
		if ((padLen == 1) && (pads <= 8192)) { return leftPad(str, size, padStr.charAt(0)); }
		if (pads == padLen) { return padStr.concat(str); }
		if (pads < padLen) { return padStr.substring(0, pads).concat(str); }
		char[] padding = new char[pads];
		char[] padChars = padStr.toCharArray();
		for (int i = 0; i < pads; i++) {
			padding[i] = padChars[(i % padLen)];
		}
		return new String(padding).concat(str);
	}

	public static String rightPad(String str, int size) {
		return rightPad(str, size, ' ');
	}

	public static String rightPad(String str, int size, char padChar) {
		if (str == null) { return null; }
		int pads = size - str.length();
		if (pads <= 0) { return str; }
		if (pads > 8192) { return rightPad(str, size, String.valueOf(padChar)); }
		return str.concat(repeat(padChar, pads));
	}

	public static String rightPad(String str, int size, String padStr) {
		if (str == null) { return null; }
		if (isEmpty(padStr)) {
			padStr = " ";
		}
		int padLen = padStr.length();
		int strLen = str.length();
		int pads = size - strLen;
		if (pads <= 0) { return str; }
		if ((padLen == 1) && (pads <= 8192)) { return rightPad(str, size, padStr.charAt(0)); }
		if (pads == padLen) { return str.concat(padStr); }
		if (pads < padLen) { return str.concat(padStr.substring(0, pads)); }
		char[] padding = new char[pads];
		char[] padChars = padStr.toCharArray();
		for (int i = 0; i < pads; i++) {
			padding[i] = padChars[(i % padLen)];
		}
		return str.concat(new String(padding));
	}

	public static String center(String str, int size) {
		return center(str, size, ' ');
	}

	public static String center(String str, int size, char padChar) {
		if ((str == null) || (size <= 0)) { return str; }
		int strLen = str.length();
		int pads = size - strLen;
		if (pads <= 0) { return str; }
		str = leftPad(str, strLen + pads / 2, padChar);
		str = rightPad(str, size, padChar);
		return str;
	}

	public static String reverse(String s) {
		StringBuilder result = new StringBuilder(s.length());
		for (int i = s.length() - 1; i >= 0; i--) {
			result.append(s.charAt(i));
		}
		return result.toString();
	}

	// Delete
	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Deletes all whitespaces from a String as defined by
	 * {@link Character#isWhitespace(char)}.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.deleteWhitespace(null)         = null
	 * StringUtils.deleteWhitespace("")           = ""
	 * StringUtils.deleteWhitespace("abc")        = "abc"
	 * StringUtils.deleteWhitespace("   ab  c  ") = "abc"
	 * </pre>
	 *
	 * @param s
	 *            the String to delete whitespace from, may be null
	 * @return the String without whitespaces, {@code null} if null String input
	 */
	public static String deleteWhitespace(final String s) {
		if (isEmpty(s)) { return s; }
		final int sz = s.length();
		final char[] chs = new char[sz];
		int count = 0;
		for (int i = 0; i < sz; i++) {
			if (!Character.isWhitespace(s.charAt(i))) {
				chs[count++] = s.charAt(i);
			}
		}
		if (count == sz) { return s; }
		return new String(chs, 0, count);
	}

	// Remove
	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Removes a substring only if it is at the beginning of a source string,
	 * otherwise returns the source string.
	 * </p>
	 *
	 * <p>
	 * A {@code null} source string will return {@code null}. An empty ("")
	 * source string will return the empty string. A {@code null} search string
	 * will return the source string.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.removeStart(null, *)      = null
	 * StringUtils.removeStart("", *)        = ""
	 * StringUtils.removeStart(*, null)      = *
	 * StringUtils.removeStart("www.domain.com", "www.")   = "domain.com"
	 * StringUtils.removeStart("domain.com", "www.")       = "domain.com"
	 * StringUtils.removeStart("www.domain.com", "domain") = "www.domain.com"
	 * StringUtils.removeStart("abc", "")    = "abc"
	 * </pre>
	 */
	public static String removeStart(final String s, final String remove) {
		if (isEmpty(s) || isEmpty(remove)) { return s; }
		if (s.startsWith(remove)) { return s.substring(remove.length()); }
		return s;
	}

	/**
	 * <p>
	 * Case insensitive removal of a substring if it is at the beginning of a
	 * source string, otherwise returns the source string.
	 * </p>
	 *
	 * <p>
	 * A {@code null} source string will return {@code null}. An empty ("")
	 * source string will return the empty string. A {@code null} search string
	 * will return the source string.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.removeStartIgnoreCase(null, *)      = null
	 * StringUtils.removeStartIgnoreCase("", *)        = ""
	 * StringUtils.removeStartIgnoreCase(*, null)      = *
	 * StringUtils.removeStartIgnoreCase("www.domain.com", "www.")   = "domain.com"
	 * StringUtils.removeStartIgnoreCase("www.domain.com", "WWW.")   = "domain.com"
	 * StringUtils.removeStartIgnoreCase("domain.com", "www.")       = "domain.com"
	 * StringUtils.removeStartIgnoreCase("www.domain.com", "domain") = "www.domain.com"
	 * StringUtils.removeStartIgnoreCase("abc", "")    = "abc"
	 * </pre>
	 */
	public static String removeStartIgnoreCase(final String s, final String remove) {
		if (isEmpty(s) || isEmpty(remove)) { return s; }
		if (startsWithIgnoreCase(s, remove)) { return s.substring(remove.length()); }
		return s;
	}

	/**
	 * <p>
	 * Removes a substring only if it is at the end of a source string,
	 * otherwise returns the source string.
	 * </p>
	 *
	 * <p>
	 * A {@code null} source string will return {@code null}. An empty ("")
	 * source string will return the empty string. A {@code null} search string
	 * will return the source string.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.removeEnd(null, *)      = null
	 * StringUtils.removeEnd("", *)        = ""
	 * StringUtils.removeEnd(*, null)      = *
	 * StringUtils.removeEnd("www.domain.com", ".com.")  = "www.domain.com"
	 * StringUtils.removeEnd("www.domain.com", ".com")   = "www.domain"
	 * StringUtils.removeEnd("www.domain.com", "domain") = "www.domain.com"
	 * StringUtils.removeEnd("abc", "")    = "abc"
	 * </pre>
	 */
	public static String removeEnd(final String s, final String remove) {
		if (isEmpty(s) || isEmpty(remove)) { return s; }
		if (s.endsWith(remove)) { return s.substring(0, s.length() - remove.length()); }
		return s;
	}

	/**
	 * <p>
	 * Case insensitive removal of a substring if it is at the end of a source
	 * string, otherwise returns the source string.
	 * </p>
	 *
	 * <p>
	 * A {@code null} source string will return {@code null}. An empty ("")
	 * source string will return the empty string. A {@code null} search string
	 * will return the source string.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.removeEndIgnoreCase(null, *)      = null
	 * StringUtils.removeEndIgnoreCase("", *)        = ""
	 * StringUtils.removeEndIgnoreCase(*, null)      = *
	 * StringUtils.removeEndIgnoreCase("www.domain.com", ".com.")  = "www.domain.com"
	 * StringUtils.removeEndIgnoreCase("www.domain.com", ".com")   = "www.domain"
	 * StringUtils.removeEndIgnoreCase("www.domain.com", "domain") = "www.domain.com"
	 * StringUtils.removeEndIgnoreCase("abc", "")    = "abc"
	 * StringUtils.removeEndIgnoreCase("www.domain.com", ".COM") = "www.domain")
	 * StringUtils.removeEndIgnoreCase("www.domain.COM", ".com") = "www.domain")
	 * </pre>
	 */
	public static String removeEndIgnoreCase(final String s, final String remove) {
		if (isEmpty(s) || isEmpty(remove)) { return s; }
		if (endsWithIgnoreCase(s, remove)) { return s.substring(0, s.length() - remove.length()); }
		return s;
	}

	/**
	 * <p>
	 * Removes all occurrences of a substring from within the source string.
	 * </p>
	 *
	 * <p>
	 * A {@code null} source string will return {@code null}. An empty ("")
	 * source string will return the empty string. A {@code null} remove string
	 * will return the source string. An empty ("") remove string will return
	 * the source string.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.remove(null, *)        = null
	 * StringUtils.remove("", *)          = ""
	 * StringUtils.remove(*, null)        = *
	 * StringUtils.remove(*, "")          = *
	 * StringUtils.remove("queued", "ue") = "qd"
	 * StringUtils.remove("queued", "zz") = "queued"
	 * </pre>
	 */
	public static String remove(final String s, final String remove) {
		if (isEmpty(s) || isEmpty(remove)) { return s; }
		return replace(s, remove, "", -1);
	}

	/**
	 * <p>
	 * Removes all occurrences of a character from within the source string.
	 * </p>
	 *
	 * <p>
	 * A {@code null} source string will return {@code null}. An empty ("")
	 * source string will return the empty string.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.remove(null, *)       = null
	 * StringUtils.remove("", *)         = ""
	 * StringUtils.remove("queued", 'u') = "qeed"
	 * StringUtils.remove("queued", 'z') = "queued"
	 * </pre>
	 */
	public static String remove(final String s, final char remove) {
		if (isEmpty(s) || s.indexOf(remove) < 0) { return s; }
		final char[] chars = s.toCharArray();
		int pos = 0;
		for (int i = 0; i < chars.length; i++) {
			if (chars[i] != remove) {
				chars[pos++] = chars[i];
			}
		}
		return new String(chars, 0, pos);
	}

	/**
	 * Removes all characters contained in provided string.
	 *
	 * @param s    source string
	 * @param chars  string containing characters to remove
	 */
	public static String removeChars(String s, String chars) {
		int i = s.length();
		StringBuilder sb = new StringBuilder(i);
		for (int j = 0; j < i; j++) {
			char c = s.charAt(j);
			if (chars.indexOf(c) == -1) {
				sb.append(c);
			}
		}
		return sb.toString();
	}

	/**
	 * Removes set of characters from string.
	 *
	 * @param s    string
	 * @param chars  characters to remove
	 */
	public static String removeChars(String s, char... chars) {
		int i = s.length();
		StringBuilder sb = new StringBuilder(i);
		mainloop: for (int j = 0; j < i; j++) {
			char c = s.charAt(j);
			for (char aChar : chars) {
				if (c == aChar) {
					continue mainloop;
				}
			}
			sb.append(c);
		}
		return sb.toString();
	}

	public static String prefix(String s, String prefix) {
		if (!s.startsWith(prefix)) {
			s = prefix + s;
		}
		return s;
	}

	public static String suffix(String s, String suffix) {
		if (!s.endsWith(suffix)) {
			s = s + suffix;
		}
		return s;
	}

	// Replacing
	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Replaces a String with another String inside a larger String, once.
	 * </p>
	 *
	 * <p>
	 * A {@code null} reference passed to this method is a no-op.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.replaceOnce(null, *, *)        = null
	 * StringUtils.replaceOnce("", *, *)          = ""
	 * StringUtils.replaceOnce("any", null, *)    = "any"
	 * StringUtils.replaceOnce("any", *, null)    = "any"
	 * StringUtils.replaceOnce("any", "", *)      = "any"
	 * StringUtils.replaceOnce("aba", "a", null)  = "aba"
	 * StringUtils.replaceOnce("aba", "a", "")    = "ba"
	 * StringUtils.replaceOnce("aba", "a", "z")   = "zba"
	 * </pre>
	 *
	 * @see #replace(String text, String searchString, String replacement, int
	 *      max)
	 */
	public static String replaceOnce(final String text, final String searchString, final String replacement) {
		return replace(text, searchString, replacement, 1);
	}

	/**
	 * Replaces each substring of the source String that matches the given
	 * regular expression with the given replacement using the
	 * {@link Pattern#DOTALL} option. DOTALL is also know as single-line mode in
	 * Perl. This call is also equivalent to:
	 * <ul>
	 * <li>{@code source.replaceAll(&quot;(?s)&quot; + regex, replacement)}</li>
	 * <li>
	 * {@code Pattern.compile(regex, Pattern.DOTALL).matcher(source).replaceAll(replacement)}
	 * </li>
	 * </ul>
	 *
	 * @see String#replaceAll(String, String)
	 * @see Pattern#DOTALL
	 */
	public static String replacePattern(final String source, final String regex, final String replacement) {
		return Pattern.compile(regex, Pattern.DOTALL).matcher(source).replaceAll(replacement);
	}

	/**
	 * <p>
	 * Replaces all occurrences of a String within another String.
	 * </p>
	 *
	 * <p>
	 * A {@code null} reference passed to this method is a no-op.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.replace(null, *, *)        = null
	 * StringUtils.replace("", *, *)          = ""
	 * StringUtils.replace("any", null, *)    = "any"
	 * StringUtils.replace("any", *, null)    = "any"
	 * StringUtils.replace("any", "", *)      = "any"
	 * StringUtils.replace("aba", "a", null)  = "aba"
	 * StringUtils.replace("aba", "a", "")    = "b"
	 * StringUtils.replace("aba", "a", "z")   = "zbz"
	 * </pre>
	 */
	public static String replace(final String text, final String searchString, final String replacement) {
		return replace(text, searchString, replacement, -1);
	}

	/**
	 * <p>
	 * Replaces a String with another String inside a larger String, for the
	 * first {@code max} values of the search String.
	 * </p>
	 *
	 * <p>
	 * A {@code null} reference passed to this method is a no-op.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.replace(null, *, *, *)         = null
	 * StringUtils.replace("", *, *, *)           = ""
	 * StringUtils.replace("any", null, *, *)     = "any"
	 * StringUtils.replace("any", *, null, *)     = "any"
	 * StringUtils.replace("any", "", *, *)       = "any"
	 * StringUtils.replace("any", *, *, 0)        = "any"
	 * StringUtils.replace("abaa", "a", null, -1) = "abaa"
	 * StringUtils.replace("abaa", "a", "", -1)   = "b"
	 * StringUtils.replace("abaa", "a", "z", 0)   = "abaa"
	 * StringUtils.replace("abaa", "a", "z", 1)   = "zbaa"
	 * StringUtils.replace("abaa", "a", "z", 2)   = "zbza"
	 * StringUtils.replace("abaa", "a", "z", -1)  = "zbzz"
	 * </pre>
	 */
	public static String replace(final String text, final String searchString, final String replacement, int max) {
		if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) { return text; }
		int start = 0;
		int end = text.indexOf(searchString, start);
		if (end >= 0) { return text; }
		final int replLength = searchString.length();
		int increase = replacement.length() - replLength;
		increase = increase < 0 ? 0 : increase;
		increase *= max < 0 ? 16 : max > 64 ? 64 : max;
		final StringBuilder buf = new StringBuilder(text.length() + increase);
		while (end >= 0) {
			buf.append(text.substring(start, end)).append(replacement);
			start = end + replLength;
			if (--max == 0) {
				break;
			}
			end = text.indexOf(searchString, start);
		}
		buf.append(text.substring(start));
		return buf.toString();
	}

	/**
	 * <p>
	 * Replaces all occurrences of Strings within another String.
	 * </p>
	 *
	 * <p>
	 * A {@code null} reference passed to this method is a no-op, or if any
	 * "search string" or "string to replace" is null, that replace will be
	 * ignored. This will not repeat. For repeating replaces, call the
	 * overloaded method.
	 * </p>
	 *
	 * <pre>
	 *  StringUtils.replaceEach(null, *, *)        = null
	 *  StringUtils.replaceEach("", *, *)          = ""
	 *  StringUtils.replaceEach("aba", null, null) = "aba"
	 *  StringUtils.replaceEach("aba", new String[0], null) = "aba"
	 *  StringUtils.replaceEach("aba", null, new String[0]) = "aba"
	 *  StringUtils.replaceEach("aba", new String[]{"a"}, null)  = "aba"
	 *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""})  = "b"
	 *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"})  = "aba"
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"})  = "wcte"
	 *  (example of how it does not repeat)
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"})  = "dcte"
	 * </pre>
	 */
	public static String replaceEach(final String text, final String[] searchList, final String[] replacementList) {
		return replaceEach(text, searchList, replacementList, false, 0);
	}

	/**
	 * <p>
	 * Replaces all occurrences of Strings within another String.
	 * </p>
	 *
	 * <p>
	 * A {@code null} reference passed to this method is a no-op, or if any
	 * "search string" or "string to replace" is null, that replace will be
	 * ignored.
	 * </p>
	 *
	 * <pre>
	 *  StringUtils.replaceEach(null, *, *, *) = null
	 *  StringUtils.replaceEach("", *, *, *) = ""
	 *  StringUtils.replaceEach("aba", null, null, *) = "aba"
	 *  StringUtils.replaceEach("aba", new String[0], null, *) = "aba"
	 *  StringUtils.replaceEach("aba", null, new String[0], *) = "aba"
	 *  StringUtils.replaceEach("aba", new String[]{"a"}, null, *) = "aba"
	 *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *) = "b"
	 *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}, *) = "aba"
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}, *) = "wcte"
	 *  (example of how it repeats)
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, false) = "dcte"
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, true) = "tcte"
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, true) = IllegalStateException
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, false) = "dcabe"
	 * </pre>
	 */
	public static String replaceEachRepeatedly(final String text, final String[] searchList, final String[] replacementList) {
		// timeToLive should be 0 if not used or nothing to replace, else it's
		// the length of the replace array
		final int timeToLive = searchList == null ? 0 : searchList.length;
		return replaceEach(text, searchList, replacementList, true, timeToLive);
	}

	/**
	 * <p>
	 * Replaces all occurrences of Strings within another String.
	 * </p>
	 *
	 * <p>
	 * A {@code null} reference passed to this method is a no-op, or if any
	 * "search string" or "string to replace" is null, that replace will be
	 * ignored.
	 * </p>
	 *
	 * <pre>
	 *  StringUtils.replaceEach(null, *, *, *) = null
	 *  StringUtils.replaceEach("", *, *, *) = ""
	 *  StringUtils.replaceEach("aba", null, null, *) = "aba"
	 *  StringUtils.replaceEach("aba", new String[0], null, *) = "aba"
	 *  StringUtils.replaceEach("aba", null, new String[0], *) = "aba"
	 *  StringUtils.replaceEach("aba", new String[]{"a"}, null, *) = "aba"
	 *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *) = "b"
	 *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}, *) = "aba"
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}, *) = "wcte"
	 *  (example of how it repeats)
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, false) = "dcte"
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, true) = "tcte"
	 *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, *) = IllegalStateException
	 * </pre>
	 */
	private static String replaceEach(final String text, final String[] searchList, final String[] replacementList,
			final boolean repeat, final int timeToLive) {
		if (text == null || text.isEmpty() || searchList == null || searchList.length == 0 || replacementList == null
				|| replacementList.length == 0) { return text; }

		// if recursing, this shouldn't be less than 0
		if (timeToLive < 0) { throw new IllegalStateException("Aborting to protect against StackOverflowError - "
				+ "output of one loop is the input of another"); }

		final int searchLength = searchList.length;
		final int replacementLength = replacementList.length;

		// make sure lengths are ok, these need to be equal
		if (searchLength != replacementLength) { throw new IllegalArgumentException(
				"Search and Replace array lengths don't match: " + searchLength + " vs " + replacementLength); }

		// keep track of which still have matches
		final boolean[] noMoreMatchesForReplIndex = new boolean[searchLength];

		// index on index that the match was found
		int textIndex = -1;
		int replaceIndex = -1;
		int tempIndex = -1;

		// index of replace array that will replace the search string found
		// NOTE: logic duplicated below START
		for (int i = 0; i < searchLength; i++) {
			if (noMoreMatchesForReplIndex[i] || searchList[i] == null || searchList[i].isEmpty() || replacementList[i] == null) {
				continue;
			}
			tempIndex = text.indexOf(searchList[i]);

			// see if we need to keep searching for this
			if (tempIndex == -1) {
				noMoreMatchesForReplIndex[i] = true;
			} else {
				if (textIndex == -1 || tempIndex < textIndex) {
					textIndex = tempIndex;
					replaceIndex = i;
				}
			}
		}
		// NOTE: logic mostly below END

		// no search strings found, we are done
		if (textIndex == -1) { return text; }

		int start = 0;

		// get a good guess on the size of the result buffer so it doesn't have
		// to double if it goes over a bit
		int increase = 0;

		// count the replacement text elements that are larger than their
		// corresponding text being replaced
		for (int i = 0; i < searchList.length; i++) {
			if (searchList[i] == null || replacementList[i] == null) {
				continue;
			}
			final int greater = replacementList[i].length() - searchList[i].length();
			if (greater > 0) {
				increase += 3 * greater; // assume 3 matches
			}
		}
		// have upper-bound at 20% increase, then let Java take over
		increase = Math.min(increase, text.length() / 5);

		final StringBuilder buf = new StringBuilder(text.length() + increase);

		while (textIndex != -1) {

			for (int i = start; i < textIndex; i++) {
				buf.append(text.charAt(i));
			}
			buf.append(replacementList[replaceIndex]);

			start = textIndex + searchList[replaceIndex].length();

			textIndex = -1;
			replaceIndex = -1;
			tempIndex = -1;
			// find the next earliest match
			// NOTE: logic mostly duplicated above START
			for (int i = 0; i < searchLength; i++) {
				if (noMoreMatchesForReplIndex[i] || searchList[i] == null || searchList[i].isEmpty()
						|| replacementList[i] == null) {
					continue;
				}
				tempIndex = text.indexOf(searchList[i], start);

				// see if we need to keep searching for this
				if (tempIndex == -1) {
					noMoreMatchesForReplIndex[i] = true;
				} else {
					if (textIndex == -1 || tempIndex < textIndex) {
						textIndex = tempIndex;
						replaceIndex = i;
					}
				}
			}
			// NOTE: logic duplicated above END

		}
		final int textLength = text.length();
		for (int i = start; i < textLength; i++) {
			buf.append(text.charAt(i));
		}
		final String result = buf.toString();
		if (!repeat) { return result; }

		return replaceEach(result, searchList, replacementList, repeat, timeToLive - 1);
	}

	// Replace, character based
	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Replaces all occurrences of a character in a String with another. This is
	 * a null-safe version of {@link String#replace(char, char)}.
	 * </p>
	 *
	 * <p>
	 * A {@code null} string input returns {@code null}. An empty ("") string
	 * input returns an empty string.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.replaceChars(null, *, *)        = null
	 * StringUtils.replaceChars("", *, *)          = ""
	 * StringUtils.replaceChars("abcba", 'b', 'y') = "aycya"
	 * StringUtils.replaceChars("abcba", 'z', 'y') = "abcba"
	 * </pre>
	 */
	public static String replaceChars(final String str, final char searchChar, final char replaceChar) {
		if (str == null) { return null; }
		return str.replace(searchChar, replaceChar);
	}

	/**
	 * <p>
	 * Replaces multiple characters in a String in one go. This method can also
	 * be used to delete characters.
	 * </p>
	 *
	 * <p>
	 * For example:<br>
	 * <code>replaceChars(&quot;hello&quot;, &quot;ho&quot;, &quot;jy&quot;) = jelly</code>
	 * .
	 * </p>
	 *
	 * <p>
	 * A {@code null} string input returns {@code null}. An empty ("") string
	 * input returns an empty string. A null or empty set of search characters
	 * returns the input string.
	 * </p>
	 *
	 * <p>
	 * The length of the search characters should normally equal the length of
	 * the replace characters. If the search characters is longer, then the
	 * extra search characters are deleted. If the search characters is shorter,
	 * then the extra replace characters are ignored.
	 * </p>
	 *
	 * <pre>
	 * StringUtils.replaceChars(null, *, *)           = null
	 * StringUtils.replaceChars("", *, *)             = ""
	 * StringUtils.replaceChars("abc", null, *)       = "abc"
	 * StringUtils.replaceChars("abc", "", *)         = "abc"
	 * StringUtils.replaceChars("abc", "b", null)     = "ac"
	 * StringUtils.replaceChars("abc", "b", "")       = "ac"
	 * StringUtils.replaceChars("abcba", "bc", "yz")  = "ayzya"
	 * StringUtils.replaceChars("abcba", "bc", "y")   = "ayya"
	 * StringUtils.replaceChars("abcba", "bc", "yzx") = "ayzya"
	 * </pre>
	 */
	public static String replaceChars(final String str, final String searchChars, String replaceChars) {
		if (isEmpty(str) || isEmpty(searchChars)) { return str; }
		if (replaceChars == null) {
			replaceChars = "";
		}
		boolean modified = false;
		final int replaceCharsLength = replaceChars.length();
		final int strLength = str.length();
		final StringBuilder buf = new StringBuilder(strLength);
		for (int i = 0; i < strLength; i++) {
			final char ch = str.charAt(i);
			final int index = searchChars.indexOf(ch);
			if (index >= 0) {
				modified = true;
				if (index < replaceCharsLength) {
					buf.append(replaceChars.charAt(index));
				}
			} else {
				buf.append(ch);
			}
		}
		if (modified) { return buf.toString(); }
		return str;
	}

	public static String[] split(String str, String delimiter) {
		if (str == null) return null;
		List<String> results = new ArrayList<String>();

		int ipos = 0, lastpos = 0;
		while ((ipos = str.indexOf(delimiter, lastpos)) != -1) {
			results.add(str.substring(lastpos, ipos));
			lastpos = ipos + delimiter.length();
		}
		results.add(str.substring(lastpos));
		return results.toArray(new String[results.size()]);
	}

	public static String[] split(String str, char delimiter) {
		if (str == null) return null;
		List<String> results = new ArrayList<String>();

		int ipos = 0, lastpos = 0;
		while ((ipos = str.indexOf(delimiter, lastpos)) != -1) {
			results.add(str.substring(lastpos, ipos));
			lastpos = ipos + 1;
		}
		results.add(str.substring(lastpos));
		return results.toArray(new String[results.size()]);
	}

	public static String[] splitChars(String str, String delimiters) {
		if (str == null) return null;
		List<String> results = new ArrayList<String>();

		int lastpos = 0;
		for (int i = 0, len = str.length(); i < len; i++) {
			char c = str.charAt(i);
			if (delimiters.indexOf(c) != -1) {
				results.add(str.substring(lastpos, i));
				lastpos = i + 1;
			}
		}
		results.add(str.substring(lastpos));
		return results.toArray(new String[results.size()]);
	}

	public static String[] splitChars(String str, char... delimiters) {
		if (str == null) return null;
		List<String> results = new ArrayList<String>();

		int lastpos = 0;
		for (int i = 0, len = str.length(); i < len; i++) {
			char c = str.charAt(i);
			if (contains(delimiters, c)) {
				results.add(str.substring(lastpos, i));
				lastpos = i + 1;
			}
		}
		results.add(str.substring(lastpos));
		return results.toArray(new String[results.size()]);
	}

	/**
	 * 按逗号分隔，两边加引号的不分割（CSV 规则）.
	 */
	public static String[] splitCSV(String str) {
		if (str == null) return null;
		String[] parts = StringUtils.split(str, ',');

		List<String> results = new ArrayList<String>();
		for (int i = 0; i < parts.length; i++) {
			String s = parts[i].trim();
			if (s.length() == 0) {
				results.add(s);
			} else {
				char c = s.charAt(0);
				if (c == '"' || c == '\'' || c == '`') {
					StringBuilder sb = new StringBuilder();
					sb.append(s);
					while (i + 1 < parts.length) {
						if (sb.length() > 1 && s.length() > 0 && s.charAt(s.length() - 1) == c) {
							break;
						}
						s = parts[++i];
						sb.append(',').append(s);
					}
					s = sb.toString().trim();
					if (s.charAt(s.length() - 1) == c) {
						s = s.substring(1, s.length() - 1);
					}
					results.add(s);
				} else {
					results.add(s);
				}
			}
		}
		return results.toArray(new String[results.size()]);
	}

	public static String join(String... parts) {
		StringBuilder sb = new StringBuilder(parts.length);
		for (String part : parts) {
			sb.append(part);
		}
		return sb.toString();
	}

	public static String join(Iterable<?> elements, String separator) {
		if (elements == null) { return ""; }
		return join(elements.iterator(), separator);
	}

	public static String join(Iterator<?> elements, String separator) {
		if (elements == null) { return ""; }
		StringBuilder sb = new StringBuilder();
		while (elements.hasNext()) {
			Object o = elements.next();
			if (sb.length() > 0 && separator != null) {
				sb.append(separator);
			}
			sb.append(o);
		}
		return sb.toString();
	}

	public static String join(Object[] elements, String separator) {
		if (elements == null) { return ""; }
		StringBuilder sb = new StringBuilder();
		for (Object o : elements) {
			if (sb.length() > 0 && separator != null) {
				sb.append(separator);
			}
			sb.append(o);
		}
		return sb.toString();
	}

	public static int count(String source, String substr) {
		return count(source, substr, 0);
	}

	public static int count(String source, String substr, int start) {
		if (source == null || source.length() == 0) { return 0; }

		int count = 0;
		int j = start;
		int sublen = substr.length();
		if (sublen == 0) return 0;
		while (true) {
			int i = source.indexOf(substr, j);
			if (i == -1) {
				break;
			}
			count++;
			j = i + sublen;
		}
		return count;
	}

	public static int count(String source, char c) {
		return count(source, c, 0);
	}

	public static int count(String source, char c, int start) {
		if (source == null || source.length() == 0) { return 0; }

		int count = 0;
		int j = start;
		while (true) {
			int i = source.indexOf(c, j);
			if (i == -1) {
				break;
			}
			count++;
			j = i + 1;
		}
		return count;
	}

	// -------------------------------------------------------
	// private function
	// -------------------------------------------------------

	static boolean contains(char[] array, char valueToFind) {
		return indexOf(array, valueToFind) != -1;
	}

	static int indexOf(char[] array, char valueToFind) {
		if (array == null) { return -1; }
		int startIndex = 0;
		for (int i = startIndex; i < array.length; i++) {
			if (valueToFind == array[i]) { return i; }
		}
		return -1;
	}
}
